package com.dhms.vostok1.model

import androidx.lifecycle.*
import com.dhms.uikit.getCalculatedDate
import com.dhms.vostok1.data.*
import com.dhms.vostok1.data.fault.*
import com.dhms.vostok1.repository.FaultRepository
import com.dhms.vostok1.service.net.NetworkApi
import com.dhms.vostok1.utils.secondTimeStamp
import com.dhms.vostok1.utils.toDate
import com.dhms.vostok1.utils.toMap
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.launch
import java.util.*

class FaultDetailViewModel(faultRepository: FaultRepository) : ViewModel() {
    private val _faultDetail = MutableLiveData<FaultDetail?>(null)
    private val _faultDiagnoseInfo = MutableLiveData<List<PartState>>()
    private val _faultRunningData = MutableLiveData<Map<String, TSDBResSourcesData>?>()
    private val _runningDataTimeRangeStart = MutableLiveData<Date?>()
    private val _runningDataTimeRangeEnd = MutableLiveData<Date?>()
    private val _faultRecordCount = MutableLiveData(defaultFaultRecordCount)

    private val _relatedFaults = MutableLiveData<Map<Float,Float>?>()
    private val _deviceFaultRecords = MutableLiveData<List<DeviceFaultRecordItem>>()
    val faultDetail: LiveData<FaultDetail?> = _faultDetail
    val faultDiagnoseInfo: LiveData<List<PartState>> = _faultDiagnoseInfo
    val runningDataTimeStart: LiveData<Date?> = _runningDataTimeRangeStart
    val runningDataTimeEnd: LiveData<Date?> = _runningDataTimeRangeEnd
    val deviceRunningData: LiveData<Map<String, TSDBResSourcesData>?> = _faultRunningData

    val relatedFaults: LiveData<Map<Float,Float>?> = _relatedFaults
    val deviceFaultRecords: LiveData<List<DeviceFaultRecordItem>?> = _deviceFaultRecords
    val faultRecordCount: LiveData<Int> = _faultRecordCount

    val faultLevelList: Flow<FaultLevelListData> = faultRepository.faultLevel
    val faultTypes: Flow<List<FaultTypeItemData>> = faultRepository.faultTypes

    private val _processFlowListData = MutableLiveData<List<ProcessFlowData>?>(listOf())
    val processFlowListData: LiveData<List<ProcessFlowData>?> =
        _processFlowListData

    fun getFaultDetail(faultID: Long) {
        viewModelScope.launch {
            val resData = NetworkApi.getFaultDetail(faultID)
            val data = resData.getOrNull()?.body()?.data
            _faultDetail.value = data
            _processFlowListData.value = data?.faExtending?.steps
            _deviceFaultRecords.value = data?.faOccurredSteps?.let {
                //                val thresholds = d.paThresholds?.split(",")
                DeviceFaultRecordItem.fillDeviceFaultRecordItem(it)
            }
            val occourredAt = data?.faOccurred?.toDate("yyyy-MM-dd hh:mm:ss")
            occourredAt?.let {
                val calenderInstance = Calendar.getInstance(TimeZone.getTimeZone("GMT+8:00"))
                calenderInstance.time = it
                calenderInstance.add(Calendar.DAY_OF_MONTH, -6)
                _runningDataTimeRangeStart.value = calenderInstance.time
                calenderInstance.time = it
                calenderInstance.add(Calendar.DAY_OF_MONTH, 1)
                _runningDataTimeRangeEnd.value = calenderInstance.time
            }

        }
    }

    fun getFaultDiagnoseInfo(faultID: Long) {
        viewModelScope.launch {
            val query = FaultDiagnoseQueryData(
                limit = 1,
                offset = 1,
                faultId = faultID
            ).toMap()
            val resData =
                query?.let { NetworkApi.getFaultDiagnoseInfo(it).getOrNull()?.body()?.data?.items }
            if (!resData.isNullOrEmpty()) {
                resData[0].partState?.let {
                    _faultDiagnoseInfo.value = it
                }
            } else {
                _faultDiagnoseInfo.value = emptyList()
            }
        }
    }

    fun getRunningData(startTime: Date, endTime: Date, deviceId: Long, metric: String) {
        val body = TSDBReqBody(
            startTime.secondTimeStamp(),
            endTime.secondTimeStamp(),
            listOf(
                TSDBDeviceReqItem(
                    downsample = "2m-max",
                    filters = listOf(
                        TSDBDeviceFilter(
                            "$deviceId",
                            "deviceid",
                            "literal_or"
                        )
                    ),
                    metric = metric,
                )
            )
        )
        viewModelScope.launch {
            val res = NetworkApi.getTSDBData(body)
            val error = res.exceptionOrNull()
            if (error != null) {
                _faultRunningData.value = null
                return@launch
            }
            _faultRunningData.value = res.getOrNull()?.body()?.data?.get(0)?.sources ?: mapOf()
        }
    }

    fun setRecordFaultsCount(count: Int) {
        _faultRecordCount.value = count
    }

    fun getRelatedFaults(startTime: Date, endTime: Date, deviceId: Long, metricNo: String?) {
        val startTimeStr = getCalculatedDate(
            startTime,
            "yyyy-MM-dd hh:mm:ss",
            Calendar.DAY_OF_MONTH,
            0
        )
        val endTimeStr = getCalculatedDate(
            endTime,
            "yyyy-MM-dd hh:mm:ss",
            Calendar.DAY_OF_MONTH,
            0
        )
        val query = RelatedFaultListQueryData(
            deviceId = deviceId,
            occurredStart = startTimeStr,
            occurredEnd = endTimeStr
        )
        query.toMap()?.let {
            viewModelScope.launch {
                val res = NetworkApi.getRelatedFaults(it)
                val error = res.exceptionOrNull()
                if (error != null) {
                    _faultRunningData.value = null
                    return@launch
                }
                val relatedFaults = HashMap<Float, Float>()
                res.getOrNull()?.body()?.data?.items?.filter {
                    it.metricNo == metricNo
                }?.forEach {
                    it.faOccurredSteps?.split("\n")?.forEach { pointStr->
                        try {
                            val pointInfo = pointStr.split(",")
                            val faultValue = pointInfo[0].toFloat()
                            val faultOccurred = pointInfo[2].toFloat()
                            relatedFaults[faultOccurred] = faultValue
                        } catch (ex: Exception) {
                            // ignore this point
                        }
                    }
                }
                _relatedFaults.value = relatedFaults.toSortedMap()
            }
        }
    }

    companion object {
        const val defaultFaultRecordCount = 5
    }
}

class FaultDetailViewModelFactory(
    private val faultRepository: FaultRepository
) : ViewModelProvider.Factory {
    override fun <T : ViewModel> create(modelClass: Class<T>): T {
        if (modelClass.isAssignableFrom(FaultDetailViewModel::class.java)) {
            @Suppress("UNCHECKED_CAST")
            return FaultDetailViewModel(faultRepository) as T
        }
        throw IllegalArgumentException("Unknown ViewModel class")
    }
}
